local last_event
local errors = {}
local params = {}
local reported, stop_on_error

local function report(s, ...)
	errors[#errors + 1] = s:format(...)
end

local check_grid2_state = function ()
	local valid_frames = Grid2Frame.registeredFrames
	local roster_units, roster_count = {}, 0
	for unit in Grid2:IterateRosterUnits() do
		roster_units[unit] = true
		roster_count = roster_count + 1
	end

	local guid_count = 0
	for guid in Grid2:IterateRoster() do
		guid_count = guid_count + 1
	end
	if guid_count ~= roster_count then
		report("%d lingering unit guids", guid_count - roster_count)
	end

	for frame in next, valid_frames do
		local unit, m_unit = frame:GetAttribute("unit"), frame.unit
		local r_unit = SecureButton_GetModifiedUnit(unit)
		if not unit and frame:IsShown() then
			report("frame %s is shown without unit", frame:GetName())
		end
		if m_unit ~= r_unit then
			report("frame %s has invalid unit value %s, should be %s (%s)", frame:GetName(), m_unit, r_unit, unit)
		end
		if m_unit then
			local found
			for f in next, Grid2:GetUnitFrames(m_unit) do
				if frame == f then found = true break end
			end
			if not found then
				report("frame %s is missing from :GetUnitFrames() for %s", frame:GetName(), m_unit)
			end
		end
	end
	for unit in next, roster_units do
		local frames = Grid2:GetUnitFrames(unit)
		local frame_count = 0
		for frame in next, frames do
			frame_count = frame_count + 1
			if not valid_frames[frame] then
				report("Unknown frame %s in :GetUnitFrames(%s)", frame:GetName(), unit)
			end
		end
		if frame_count > 2 then -- self + pet (if modified for vehicle and in vehicle)
		-- might occur a lot for some layouts
			report("too many frames for unit %s (%d)", unit, frame_count)
		end
	end

	return next(errors)
end

local function pre_check(name, ...)
	last_event = name
	local n = select("#", ...)
	for i = 1, n do
		local v = select(i, ...)
		if type(v) == "table" and v.GetName then
			params[i] = v:GetName()
		else
			params[i] = v
		end
	end
	params[n + 1] = nil
	if not reported and check_grid2_state() then
		Grid2Frame:ListRegisteredFrames()
		print("GRID2 PRE CHECK", last_event, unpack(params))
		for _, err in ipairs(errors) do
			print(err)
		end
		errors = {}
		reported = true
		if stop_on_error then return false end-- we can remove the call here if needed
	end
	return true
end

local function post_check(...)
	if not reported and check_grid2_state() then
		Grid2Frame:ListRegisteredFrames()
		print("GRID2 POST CHECK", last_event, unpack(params))
		for _, err in ipairs(errors) do
			print(err)
		end
		errors = {}
		reported = true
	end
	return ...
end

local function hook(table, method)
	local map = {
		T = table,
		M = method
	}
	local handler = loadstring(([[
local pre_check = pre_check
local post_check = post_check
local prev = $T.$M
$T.$M = function (...)
	if not pre_check("$T_$M", ...) then return end
	return post_check(prev(...))
end
	]]):gsub("$([MT])", map))
	setfenv(handler, {
		table = table,
		pre_check = pre_check,
		post_chec = post_check,
	})
	handler()
end

hook("Grid2", "UpdateRoster")
hook("Grid2", "UNIT_NAME_UPDATE")
hook("Grid2", "UNIT_PET")
hook("Grid2", "SetFrameUnit")
hook("Grid2Layout", "LoadLayout")

SlashCmdList.JERRY_G2CHECK = function (input)
	if input == "reset" then
		reported = false
	elseif input:sub(1, 4) == "hook" then
		local t, m = input:match("hook%s+([^%s]+)%s+([^%s]+)")
		if t and m then
			hook(t, m)
		end
	elseif input == "stop" then
		stop_on_error = not stop_on_error
	else
		pre_check("manual")
		post_check()
	end
end

SLASH_JERRY_G2CHECK1="/g2check"
